package com.kehutong.common.excel.listener;


import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.read.metadata.holder.ReadHolder;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty;
import org.coraframework.logger.Logger;
import org.coraframework.logger.LoggerFactory;
import org.coraframework.util.Objects;

import javax.validation.ConstraintViolation;
import javax.validation.Path;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;

/**
 * Excel import listener
 * @author jian Ye
 * @Date  2021/7/30
 **/
@SuppressWarnings("unchecked")
public class ExcelImportListener<T> extends AnalysisEventListener<T> {

    private static final Logger log = LoggerFactory.getLogger(ExcelImportListener.class);
    private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();


    private Class<T> clazz;
    private List<T> list = new ArrayList<>();

    public ExcelImportListener(Class<T> clazz) {
        this.clazz = clazz;
    }

    public List<T> getList() {
        return list;
    }
    public void setList(List<T> list) {
        this.list = list;
    }

    public Class<T> getClazz() {
        return clazz;
    }

    public void setClazz(Class<T> clazz) {
        this.clazz = clazz;
    }




    @Override
    public void invoke(T t, AnalysisContext context) {
        if (validate(t,context)) {
            list.add(t);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    }

    private boolean validate(T o, AnalysisContext context) {
        Set<ConstraintViolation<T>> validateSet = VALIDATOR.validate(o, Default.class);
        if (Objects.isEmpty(validateSet)) {
            return true;
        }
        list.clear();
        StringBuilder sb = new StringBuilder();
        Map<String, SoftReference<ExcelCellBo>> propertyNameMap = this.getPropertyNameMap(true,context);
        for (ConstraintViolation<T> constraint : validateSet) {
            Path propertyPath = constraint.getPropertyPath();
            String propertyName = propertyPath.toString();
            SoftReference<ExcelCellBo> bo = propertyNameMap.get(propertyName);
            if(null == bo){
                String error = BindTransfer.castFieldName(clazz);
                throw new ExcelAnalysisException(error);
            }
            Integer rowIndex = bo.get().getRowIndex()+1;
            Integer columnIndex = bo.get().getColumnIndex()+1;
            String message = constraint.getMessage();
            log.warn("[Excel导入]导入失败的数据validate未通过 [row:{}],[column:{}],[message:{}]",rowIndex, columnIndex, message);
            String format = String.format("请确认第[%s]行 第[%s]列，%s", rowIndex, columnIndex, message);
            sb.append(format).append("\n");
        }
        throw new ExcelAnalysisException(sb.toString());
    }


    private Map<String, SoftReference<ExcelCellBo>> getPropertyNameMap(boolean isSingleHeader, AnalysisContext analysisContext){
        ReadRowHolder readRowHolder = analysisContext.readRowHolder();
        Integer rowIndex = readRowHolder.getRowIndex();
        if(null == rowIndex){
            String error = BindTransfer.castFieldName(clazz);
            throw new ExcelAnalysisException(error);
        }
        ReadHolder readHolder = analysisContext.currentReadHolder();
        ExcelReadHeadProperty excelReadHeadProperty = readHolder.excelReadHeadProperty();
        Collection<ExcelContentProperty> values;
        if (isSingleHeader){
            Map<Integer, ExcelContentProperty> contentPropertyMap = excelReadHeadProperty.getContentPropertyMap();
            values = contentPropertyMap.values();
        }else {
            //也适用于单头
            Map<String, ExcelContentProperty> fieldNameContentPropertyMap = excelReadHeadProperty.getFieldNameContentPropertyMap();
            values = fieldNameContentPropertyMap.values();
        }
        BigDecimal initSize = div(BigDecimal.valueOf(values.size()),BigDecimal.valueOf(0.75));
        Map<String, SoftReference<ExcelCellBo>> propertyNameMap = new HashMap<>(initSize.intValue()+1);
        ExcelCellBo bo;
        for (ExcelContentProperty contentProperty : values) {
            bo = new ExcelCellBo();
            bo.setRowIndex(rowIndex);
            bo.setColumnIndex(contentProperty.getHead().getColumnIndex());
            bo.setFieldName(contentProperty.getHead().getFieldName());
            bo.setHeadName(String.join(",",contentProperty.getHead().getHeadNameList()));
            bo.setField(contentProperty.getField());
            propertyNameMap.put(contentProperty.getHead().getFieldName(),new SoftReference<>(bo));
        }
        return propertyNameMap;
    }


    private static BigDecimal div(BigDecimal bigDecimal, BigDecimal number) {
        return bigDecimal.divide(number, 0, BigDecimal.ROUND_DOWN);
    }


    public class ExcelCellBo {
        private Field field;
        private String fieldName;
        private String headName;
        private Integer columnIndex;
        private Integer rowIndex;

        public Field getField() {
            return field;
        }

        public void setField(Field field) {
            this.field = field;
        }

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getHeadName() {
            return headName;
        }

        public void setHeadName(String headName) {
            this.headName = headName;
        }

        public Integer getColumnIndex() {
            return columnIndex;
        }

        public void setColumnIndex(Integer columnIndex) {
            this.columnIndex = columnIndex;
        }

        public Integer getRowIndex() {
            return rowIndex;
        }

        public void setRowIndex(Integer rowIndex) {
            this.rowIndex = rowIndex;
        }
    }
}